package smetana.modules.io;

import com.google.inject.Inject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import smetana.domain.raw.*;
import smetana.modules.business.*;
import smetana.modules.io.dbfimpot.*;
import smetana.util.dbf.DBFReader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.Callable;

/**
 * @author dmitry.mamonov
 */
public class DbfImportServiceImpl implements DbfImportService {
    @Inject
    BusinessAgent businessAgent;

    @Inject
    BusinessManager businessManager;

    @Inject
    BusinessDoc businessDoc;

    @Inject
    BusinessDocPosition businessDocPosition;

    @Inject
    BusinessPosition businessPosition;

    @Override
    public ExternalModel loadExternalModelFromDirectory(final File baseDirectory, ProgressListener passedProgressListener) throws IOException {
        final ProgressListener progressListener = passedProgressListener != null
                ? passedProgressListener
                : new ProgressListener.DummyProgressListener();
        return new Callable<ExternalModel>() {
            ExternalModel result = new ExternalModel();
            int progress = 0;

            public void incrementProgress() {
                progressListener.progress(++progress);
            }

            protected void process(String fileName, DBFReaderProcessor processor) throws IOException {
                InputStream input = null;
                try {
                    input = new FileInputStream(new File(baseDirectory, fileName + ".dbf"));
                    DBFReader dbfReader = new DBFReader(input);
                    dbfReader.setCharactersetName("cp1251");

                    int columnsCount = dbfReader.getFieldCount();
                    List<String> columns = new ArrayList<String>();
                    for (int i = 0; i < columnsCount; i++) {
                        columns.add(dbfReader.getField(i).getName().toLowerCase());
                    }
                    Object[] row;
                    while ((row = dbfReader.nextRecord()) != null) {
                        processor.process(row, columns);
                        incrementProgress();
                    }
                } finally {
                    if (input != null) {
                        IOUtils.closeQuietly(input);
                    }
                }
            }

            @Override
            public ExternalModel call() throws IOException {
                if (baseDirectory.exists() == false) {
                    throw new IOException("Нет директории: " + baseDirectory);
                }
                if (baseDirectory.isDirectory() == false) {
                    throw new IOException("Не директория: " + baseDirectory);
                }

                process("agents", new DBFReaderProcessor() {
                    int indexCode;
                    int indexName;
                    int indexAddress;
                    boolean initialized = false;

                    @Override
                    public void process(Object[] row, List<String> columns) throws IOException {
                        if (initialized == false) {
                            indexCode = columns.indexOf("code");
                            indexName = columns.indexOf("name");
                            indexAddress = columns.indexOf("adress"); //yes, "adress" not "address".
                            initialized = true;
                        }

                        ExternalAgents bean = new ExternalAgents();
                        bean.setCode(str(row[indexCode]));
                        bean.setName(str(row[indexName]));
                        bean.setAddress(str(row[indexAddress]));
                        result.addAgents(bean);
                    }
                });

                process("brigaders", new DBFReaderProcessor() {
                    int indexCodeb;
                    int indexNameb;
                    boolean initialized = false;

                    @Override
                    public void process(Object[] row, List<String> columns) throws IOException {
                        if (initialized == false) {
                            indexCodeb = columns.indexOf("codeb");
                            indexNameb = columns.indexOf("nameb");
                            initialized = true;
                            System.out.println("" + columns);
                        }
                        //System.out.println(Arrays.toString(row));
                        ExternalBrigaders bean = new ExternalBrigaders();
                        bean.setCodeb(str(row[indexCodeb]));
                        bean.setNameb(str(row[indexNameb]));
                        result.addBrigaders(bean);
                    }
                });

                process("goods", new DBFReaderProcessor() {
                    int indexCode_t;
                    int indexVid;
                    int indexCodeg;
                    int indexName;
                    int indexEd;
                    int indexPrice;
                    boolean initialized = false;

                    int index = 0;

                    @Override
                    public void process(Object[] row, List<String> columns) throws IOException {
                        if (initialized == false) {
                            indexCode_t = columns.indexOf("code_t");
                            indexVid = columns.indexOf("vid");
                            indexCodeg = columns.indexOf("codeg");
                            indexName = columns.indexOf("name");
                            indexEd = columns.indexOf("ed");
                            indexPrice = columns.indexOf("price");
                            initialized = true;
                        }
                        //System.out.println((++index)+""+Arrays.toString(row));
                        ExternalGoods bean = new ExternalGoods();
                        bean.setCode_t(str(row[indexCode_t]));
                        bean.setVid(str(row[indexVid]));
                        bean.setCodeg(str(row[indexCodeg]));
                        bean.setName(str(row[indexName]));
                        bean.setEd(str(row[indexEd]));
                        bean.setPrice(str(row[indexPrice]));
                        result.addGoods(bean);

                    }
                });

                process("document", new DBFReaderProcessor() {
                    int indexProvn; // id,
                    int indexDatedoc; //date_actual
                    int indexCode_a; //agent_id
                    int indexRemark; //description,
                    int indexObject; //contract_note,
                    int indexAdress; //address,
                    int indexNomer_hand; //user_code,
                    int indexCodeb; // manager_id
                    boolean initialized = false;

                    @Override
                    public void process(Object[] row, List<String> columns) throws IOException {
                        if (initialized == false) {
                            indexProvn = columns.indexOf("provn"); // id,
                            indexDatedoc = columns.indexOf("datedoc"); //date_actual
                            indexCode_a = columns.indexOf("code_a"); //agent_id
                            indexRemark = columns.indexOf("remark"); //description,
                            indexObject = columns.indexOf("object"); //contract_note,
                            indexAdress = columns.indexOf("adress"); //address,
                            indexNomer_hand = columns.indexOf("nomer_hand"); //user_code,
                            indexCodeb = columns.indexOf("codeb"); // manager_id
                            initialized = true;
                        }
                        ExternalDocument bean = new ExternalDocument();
                        bean.setProvn(str(row[indexProvn]));
                        bean.setDatedoc((Date) row[indexDatedoc]);
                        bean.setCode_a(str(row[indexCode_a]));
                        bean.setRemark(str(row[indexRemark]));
                        bean.setObject(str(row[indexObject]));
                        bean.setAdress(str(row[indexAdress]));
                        bean.setNomer_hand(str(row[indexNomer_hand]));
                        bean.setCodeb(str(row[indexCodeb]));
                        result.addDocument(bean);
                    }
                });

                process("documin", new DBFReaderProcessor() {
                    int indexProvn; // document_id,
                    int indexVid; // product_type_id,
                    int indexCode_t; // product_id,
                    int indexKol; // amount,
                    int indexCena; // concrete_price
                    boolean initialized = false;

                    @Override
                    public void process(Object[] row, List<String> columns) throws IOException {
                        if (initialized == false) {
                            indexProvn = columns.indexOf("provn");
                            indexVid = columns.indexOf("vid");
                            indexCode_t = columns.indexOf("code_t");
                            indexKol = columns.indexOf("kol");
                            indexCena = columns.indexOf("cena");
                            initialized = true;
                        }
                        ExternalDocumin bean = new ExternalDocumin();
                        bean.setProvn(str(row[indexProvn]));
                        bean.setVid(str(row[indexVid]));
                        bean.setCode_t(str(row[indexCode_t]));
                        bean.setKol(str(row[indexKol]));
                        bean.setCena(str(row[indexCena]));
                        result.addDocumin(bean);
                    }
                });

                return result;
            }

            protected String str(Object obj) {
                if (obj == null) {
                    return "";
                } else {
                    return ("" + obj).trim();
                }
            }
        }.call();
    }

    @Override
    public void storeExternalModel(final ExternalModel model, ProgressListener passedProgressListener) {
        final ProgressListener progressListener = passedProgressListener != null
                ? passedProgressListener
                : new ProgressListener.DummyProgressListener();
        new Runnable() {
            Map<String, Agent> agentsMap;
            Map<String, Manager> managersMap;
            Map<String, Doc> docMap;
            Nearest<PositionUnit> positionUnitNearest;
            Nearest<PositionType> positionTypeNearest;

            int progress = 0;

            public void incrementProgress() {
                progressListener.progress(++progress);
            }
            

            @Override
            public void run() {
                agentsMap = importAgents();
                managersMap = importManagers();
                docMap = importDocs();
                positionUnitNearest = loadUnits();
                positionTypeNearest = loadTypes();
                importDocPositions();
            }

            private void importDocPositions() {
                int orderNum = 0;
                Map<String, Position> positionMap = new LinkedHashMap<String, Position>();
                for (ExternalDocumin extItem : model.listDocumins()) {
                    Doc doc = docMap.get(extItem.getProvn());
                    if (doc != null) {
                        try {
                            Position position;
                            { //try to setup corresponding position.
                                position = positionMap.get(extItem.getCode_t());
                                if (position == null) {
                                    //try to save position.
                                    ExternalGoods extGood = model.getGoods(extItem.getCode_t());
                                    if (extGood == null) {
                                        throw new IllegalStateException("ignore, no corresponding position: " + extItem);
                                    }
                                    position = businessPosition.newPositionUnsaved();
                                    position.setPrice(new BigDecimal(extGood.getPrice()));
                                    position.setPositionGroupId(null); //TODO
                                    PositionType positionType = positionTypeNearest.getNearest(extGood.getVid());
                                    if (positionType == null) {
                                        throw new IllegalStateException("ignore, no corresponding position type: " + extGood + ", " + extItem);
                                    }
                                    position.setPositionTypeId(positionType.getId());
                                    position.setPositionName(extGood.getName());
                                    PositionUnit positionUnit = positionUnitNearest.getNearest(extGood.getEd());
                                    if (positionUnit == null) {
                                        throw new IllegalStateException("ignore, no corresponding position unit: " + extGood + ", " + extItem);
                                    }
                                    position.setPositionUnitId(positionUnit.getId());
                                    position = businessPosition.newPosition(position);
                                    positionMap.put(extGood.getPrimary(), position);
                                }
                            }
                            DocPosition docPosition = businessDocPosition.newDocPosition(doc);
                            docPosition.setPositionId(position.getId());
                            docPosition.setAmount(new BigDecimal(extItem.getKol()));
                            docPosition.setOrderNum(++orderNum);
                            docPosition.setRealPrice(new BigDecimal(extItem.getCena()));
                            businessDocPosition.saveDocPosition(docPosition);
                            incrementProgress();
                        } catch (Exception ex) {
                            System.out.println("" + ex.getMessage() + " ON " + extItem);
                            doc.setContractNote("(Загружен частично)");
                            businessDoc.saveDoc(doc);
                        }
                    } else {
                        System.out.println("ignore, no corresponding doc: " + extItem);
                    }
                }
            }

            private Nearest<PositionUnit> loadUnits() {
                Map<String, PositionUnit> positionUnitMap = new LinkedHashMap<String, PositionUnit>();
                for (PositionUnit unit : businessPosition.listPositionUnits()) {
                    positionUnitMap.put(unit.getUnitName(), unit);
                }
                Nearest<PositionUnit> positionUnitNearest = new Nearest<PositionUnit>(positionUnitMap);
                return positionUnitNearest;
            }

            private Nearest<PositionType> loadTypes() {
                Map<String, PositionType> positionTypeMap = new LinkedHashMap<String, PositionType>();
                for (PositionType type : businessPosition.listPositionTypes()) {
                    positionTypeMap.put(type.getTypeName(), type);
                }
                Nearest<PositionType> positionTypeNearest = new Nearest<PositionType>(positionTypeMap);
                return positionTypeNearest;
            }

            private Map<String, Doc> importDocs() {
                Map<String, Doc> docMap = new HashMap<String, Doc>();
                for (ExternalDocument extDoc : model.listDocuments()) {
                    Doc doc = businessDoc.newDoc();
                    doc.setAddress(extDoc.getAdress());
                    Agent agent = agentsMap.get(extDoc.getCode_a());
                    doc.setAgentId(agent != null ? agent.getId() : null);
                    doc.setContractNote(extDoc.getObject());
                    if (extDoc.getDatedoc() != null) {
                        doc.setDateActual(new java.sql.Date(extDoc.getDatedoc().getTime()));
                    }
                    doc.setDescription(extDoc.getRemark());
                    Manager manager = managersMap.get(extDoc.getCodeb());
                    doc.setManagerId(manager != null ? manager.getId() : null);
                    doc.setUserCode(extDoc.getNomer_hand());
                    businessDoc.saveDoc(doc);
                    incrementProgress();
                    docMap.put(extDoc.getPrimary(), doc);
                }
                return docMap;
            }

            private Map<String, Manager> importManagers() {
                Map<String, Manager> managersMap = new HashMap<String, Manager>();
                for (ExternalBrigaders extBreg : model.listBrigaders()) {
                    Manager manager = businessManager.newManager(extBreg.getNameb());
                    incrementProgress();
                    managersMap.put(extBreg.getPrimary(), manager); //.0 added intentionally.
                }
                return managersMap;
            }

            private Map<String, Agent> importAgents() {
                Map<String, Agent> agentsMap = new HashMap<String, Agent>();
                for (ExternalAgents extAgent : model.listAgents()) {
                    Agent agent = businessAgent.newAgent(extAgent.getName());
                    agent.setAddress(extAgent.getAddress());
                    businessAgent.saveAgent(agent);
                    incrementProgress();
                    agentsMap.put(extAgent.getPrimary(), agent);
                }
                return agentsMap;
            }
        }.run();
    }


    private interface DBFReaderProcessor {
        void process(Object[] row, List<String> columns) throws IOException;
    }

    private static class Nearest<T> {
        Map<String, T> base;

        private Nearest(Map<String, T> base) {
            this.base = base;
        }

        public T getNearest(String match) {
            int minDistance = Integer.MAX_VALUE;
            String minKey = null;
            for (String key : base.keySet()) {
                int distance = StringUtils.getLevenshteinDistance(key, match);
                if (distance < minDistance) {
                    minKey = key;
                    minDistance = distance;
                }
            }
            return base.get(minKey);
        }
    }
}
